//=====================================================================
//
//  File:      IRowsetFastLoadUpload.cpp
//  Summary:   This sample uses the IRowsetFastUpload interface to send data to a filestream column.
// 
//             When you run the sample, you must pass two parameters: the server name 
//             (or server\instance_name) and the the amount of data to write to the database.  
//             The amount of data can be between 4 MB (0x400001) and 4 GB (0xFFFFFFFF).
// 
//             The server must have a directory called C:\DBFsa; this is where the database is created.
// 
//  Date:      September 23, 2007
//
//---------------------------------------------------------------------
//
//  This file is part of the Microsoft SQL Server Code Samples.
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation.  See these other
//materials for detailed information regarding Microsoft code samples.
//
//THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//
//===================================================================== 

#include "stdafx.h"
#include "ISSHelper.h"
#include <assert.h>
#include "prefast.h"

#define SAFE_RELEASE(x)    { if (x) { x->Release(); x = NULL; } }
#define SAFE_SYSFREE(bstr) { if (bstr) { ::SysFreeString(bstr); bstr = NULL; } }
#define CHECK_HR(hr,jump)  { if ( FAILED(hr) ) goto jump; }
#define CHECK_HR_ERR(hr,jump)  { if ( FAILED(hr) ) { DumpErrorRecords(); goto jump; } }

// Helper function for DumpErrorRecords() to convert a clisd to string representation of clsid.
LPWSTR CLSIDToString( CLSID* pclsid )
{
   static WCHAR wszCLSID[1024];
   if ( NULL == pclsid )
   {
      wsprintfW( wszCLSID, L"(Null)" );
   }
   else
   {
      ::StringFromGUID2( *pclsid, (OLECHAR*)wszCLSID, sizeof(wszCLSID) );
   }
   return wszCLSID;
}

// Helper function to dump out OLE DB errors to console window.
HRESULT DumpErrorRecords()
{
   HRESULT hr = S_OK;
   IErrorRecords* pIErrorRecords = NULL;
   IErrorInfo* pIErrorInfo		  = NULL;
   IErrorInfo* pIErrorInfoRecord = NULL;
   BSTR bstrDescription		  = NULL;
   BSTR bstrSource				  = NULL;
   ULONG i;
   ULONG cRecords = 0;
   LCID lcid = 0;
   ERRORINFO ErrorInfo;

   // Get IErrorInfo pointer from OLE.
   hr = ::GetErrorInfo( 0, &pIErrorInfo );
   if ( hr != S_OK )
   {
      printf( "No error records found.\n" );
      goto DumpErrorRecordsExit;
   }

   // QI for IID_IErrorRecords.
   hr = pIErrorInfo->QueryInterface( IID_IErrorRecords, (void**)&pIErrorRecords );
   if ( FAILED(hr) )
   {
      printf( "pIErrorInfo->QueryInterface(IID_IErrorRecords) failed with hr=0x%08x.\n", hr );
      goto DumpErrorRecordsExit;
   }

   // Get error record count.
   hr = pIErrorRecords->GetRecordCount( &cRecords );
   if ( FAILED(hr) )
   {
      printf( "pIErrorRecords->GetRecordCount() failed with hr=0x%08x.\n", hr );
      goto DumpErrorRecordsExit; 
   }

   // Get system LCID
   lcid = GetSystemDefaultLCID(); 

   //	Loop through the error records.
   for( i=0; i<cRecords; i++ )
   {
      // Get pIErrorInfo from pIErrorRecords.
      hr = pIErrorRecords->GetErrorInfo( i, lcid, &pIErrorInfoRecord );

      if ( SUCCEEDED(hr) )
      {
         // Get error description and source.
         hr = pIErrorInfoRecord->GetDescription( &bstrDescription );
         hr = pIErrorInfoRecord->GetSource( &bstrSource );

         // Retrieve the ErrorInfo structures.
         hr = pIErrorRecords->GetBasicErrorInfo( i, &ErrorInfo );

         // Dump out error.
         printf( "\n" );
         printf( "Dumping error record %lu of %lu:\n", i+1, cRecords );
         printf( "--------------------------------------------------------------------------\n" );
         printf( "   Description = %S\n", bstrDescription );
         printf( "   Source      = %S\n", bstrSource );
         printf( "   ERRORINFO.hrError = 0x%08x\n", ErrorInfo.hrError );
         printf( "   ERRORINFO.dwMinor = %lu\n",	ErrorInfo.dwMinor ); 
         printf( "   ERRORINFO.clsid   = %S\n",	CLSIDToString( &ErrorInfo.clsid ) );

         SAFE_RELEASE(pIErrorInfoRecord);
         SAFE_SYSFREE(bstrDescription);
         SAFE_SYSFREE(bstrSource);
      }
   }

DumpErrorRecordsExit:

   SAFE_RELEASE(pIErrorInfoRecord);
   SAFE_RELEASE(pIErrorRecords);
   SAFE_RELEASE(pIErrorInfo);
   SAFE_RELEASE(pIErrorInfo);
   SAFE_SYSFREE(bstrDescription);
   SAFE_SYSFREE(bstrSource);
   return hr;
}

// to open a session

// 1) create a properties object
// 2) set properties on it (such as INIT_DATASOURCE, etc.)
// 3) queryinterface an IDBInitialize on the properties object
// 4) call IDBInitialize::Initialize
// 5) queryinterface an IDBCreateSession on the IDBInitialize instance
// 6) call IDBCreateSession::CreateSession with interface to use on the session (IDBCreateCommand, IOpenRowset, IBCPSession, etc.)

HRESULT OpenSession( wchar_t* datasource, wchar_t* catalog, const IID& iid, void** out )
{
#define SET_BSTR_PROP(index, proparray, propid, value) \
   { proparray[index].dwOptions = DBPROPOPTIONS_REQUIRED; \
   proparray[index].colid = DB_NULLID; \
   proparray[index].dwStatus = DBPROPSTATUS_OK; \
   proparray[index].dwPropertyID = propid; \
   proparray[index].vValue.vt = VT_BSTR; \
   proparray[index].vValue.bstrVal = SysAllocString(value); }

#define SET_BOOL_PROP(index, proparray, propid, value) \
   { proparray[index].dwOptions = DBPROPOPTIONS_REQUIRED; \
   proparray[index].colid = DB_NULLID; \
   proparray[index].dwStatus = DBPROPSTATUS_OK; \
   proparray[index].dwPropertyID = propid; \
   proparray[index].vValue.vt = VT_BOOL; \
   proparray[index].vValue.boolVal = (value) ? VARIANT_TRUE : VARIANT_FALSE; }

   HRESULT hr;

   static IDBProperties*      pIDBProperties = NULL;
   IDBProperties*      pIDBSessionProperties = NULL;
   IDBInitialize*		pIDBInitialize = NULL;  
   static IDBCreateSession*	pIDBCreateSession = NULL;

   DBPROP				InitProperties[3];
   DBPROP              BulkCopyProperties[2];
   DBPROPSET			PropSet[2];

   hr = CoCreateInstance( SQLNCLI_CLSID, NULL, CLSCTX_INPROC_SERVER, IID_IDBProperties, 
      (void **) &pIDBProperties );    
   CHECK_HR_ERR(hr,OpenSessionCleanup);

   // Hard coded this to point to local sql server, change as needed.
   SET_BSTR_PROP( 0, InitProperties, DBPROP_INIT_DATASOURCE, datasource );
   SET_BSTR_PROP( 1, InitProperties, DBPROP_INIT_CATALOG, catalog );
   SET_BSTR_PROP( 2, InitProperties, DBPROP_AUTH_INTEGRATED, L"SSPI" );

   SET_BOOL_PROP( 0, BulkCopyProperties, SSPROP_ENABLEFASTLOAD, true );
   SET_BOOL_PROP( 1, BulkCopyProperties, SSPROP_ENABLEBULKCOPY, true );

   PropSet[0].guidPropertySet = DBPROPSET_DBINIT; 
   PropSet[0].cProperties = 3;               
   PropSet[0].rgProperties = InitProperties; 

   PropSet[1].guidPropertySet = DBPROPSET_SQLSERVERDATASOURCE;
   PropSet[1].cProperties = 2;
   PropSet[1].rgProperties = BulkCopyProperties;

   pIDBProperties->SetProperties( 1, (DBPROPSET*) &PropSet );
   CHECK_HR_ERR( hr, OpenSessionCleanup );

   hr = pIDBProperties->QueryInterface( IID_IDBInitialize, 
      (void**) &pIDBInitialize );
   CHECK_HR_ERR( hr,OpenSessionCleanup );

   hr = pIDBInitialize->Initialize();
   CHECK_HR_ERR( hr,OpenSessionCleanup );

   // QI for IID_IDBCreateSession and then create session.
   hr = pIDBInitialize->QueryInterface( IID_IDBCreateSession,
      (void**) &pIDBCreateSession );
   CHECK_HR_ERR( hr,OpenSessionCleanup );

   pIDBCreateSession->QueryInterface( IID_IDBProperties, (void**) &pIDBSessionProperties );
   pIDBSessionProperties->SetProperties( 1, &PropSet[1] );

   assert( pIDBCreateSession != NULL );
   hr = pIDBCreateSession->CreateSession( NULL, iid, 
      (IUnknown**) out );

OpenSessionCleanup:

   SAFE_RELEASE( pIDBProperties );
   SAFE_RELEASE( pIDBInitialize );
   SAFE_RELEASE( pIDBCreateSession );
   SAFE_RELEASE( pIDBSessionProperties );

   return hr;    	
}

// Helper function to execute a sql command.
HRESULT ExecuteSQL( IDBCreateCommand* pIDBCreateCommand, LPCOLESTR pwszCommand  )
{
   HRESULT hr;
   ICommandText* pICommandText = NULL;
   hr = pIDBCreateCommand->CreateCommand( NULL, IID_ICommandText, (IUnknown**)&pICommandText );
   CHECK_HR_ERR(hr,ExecuteSQLCleanup);
   hr = pICommandText->SetCommandText( DBGUID_DBSQL, pwszCommand );
   CHECK_HR_ERR(hr,ExecuteSQLCleanup);
   hr = pICommandText->Execute( NULL, IID_NULL, NULL, NULL, NULL );

ExecuteSQLCleanup:
   SAFE_RELEASE(pICommandText);
   return hr;
}

void wmain(int argc, wchar_t* argv[])
{
   HRESULT					hr;
   IOpenRowset*            pIOpenRowset = NULL;
   IRowsetFastLoad*        pIRowsetFastLoad = NULL;
   IDBCreateCommand*       pIDBCreateCommand = NULL;
   IAccessor*				pIAccessor = NULL;

   HACCESSOR				hAccessor;
   DBBINDING				dbBind[ 2 ];
   DBBINDSTATUS            dbBindStatus[ 2 ];

   if( argc < 3 ) {
      wprintf( L"Usage: IRowsetFastLoadUpload <server> <size>\n");
      return;
   }

   unsigned __int64 size = _wcstoui64( argv[2], NULL, 16 ) ;
   if( size <= (1024*1024*4) ) {
      wprintf( L"size must be at least 4MB (either it's too small or it's not a well formed number)\n" );
      return;
   }

   CISSHelper issHelper1( size );

   CoInitialize(NULL);

   // Open connection to SS database for the command
   hr = OpenSession( argv[1], L"master", IID_IDBCreateCommand, (void**) &pIDBCreateCommand );
   CHECK_HR_ERR(hr,MainCleanup);

   // Enable filestream ,drop and re-create test table and procedure.
   hr = ExecuteSQL( pIDBCreateCommand, L"exec sp_filestream_configure 3" );
   CHECK_HR_ERR( hr, MainCleanup );
   hr = ExecuteSQL( pIDBCreateCommand, L"reconfigure" );
   CHECK_HR_ERR( hr, MainCleanup );
   hr = ExecuteSQL( pIDBCreateCommand, L"IF EXISTS (SELECT name FROM master..sysdatabases WHERE name = 'DBFsa') DROP DATABASE [DBFsa]");
   CHECK_HR_ERR(hr, MainCleanup);
   hr = ExecuteSQL( pIDBCreateCommand, L"IF NOT EXISTS (SELECT name FROM sysdatabases WHERE name = 'DBFsa')"
      L"CREATE DATABASE [DBFsa] ON PRIMARY (NAME=[dbf], FILENAME='c:\\DBFsa\\dbf.mdf')," 
      L"FILEGROUP [FsGrp] CONTAINS FILESTREAM (NAME=[FsCnt], FILENAME='c:\\DBFsa\\FsCnt')"
      L"LOG ON (NAME = [dbl], FILENAME = 'c:\\DBFsa\\dbl.ldf', SIZE = 5MB, MAXSIZE = 3000MB, FILEGROWTH = 5MB)");
   CHECK_HR_ERR(hr, MainCleanup);
   hr = ExecuteSQL( pIDBCreateCommand, L"IF NOT EXISTS(SELECT name FROM [DBFsa]..[sysobjects] WHERE name='FSTRTable' AND type='U') "
      L"CREATE TABLE [DBFsa]..[FSTRTable] (uid int NOT NULL UNIQUE NONCLUSTERED, val VARBINARY(MAX) FILESTREAM, "
      L"rowguid uniqueidentifier ROWGUIDCOL NOT NULL UNIQUE DEFAULT NEWID())");
   CHECK_HR_ERR(hr, MainCleanup);
   printf( "Creation of table+proc via ExecuteSQL(...) returned 0x%08x.\n", hr );

   // Open connection to SS database for the IOpenRowset
   hr = OpenSession( argv[1], L"master", IID_IOpenRowset, (void**) &pIOpenRowset );
   CHECK_HR_ERR(hr,MainCleanup);

   DBID table;
   table.eKind = DBKIND_NAME;
   table.uName.pwszName = L"[DBFsa]..[FSTRTable]";

   hr = pIOpenRowset->OpenRowset( NULL, &table, NULL, IID_IRowsetFastLoad, 0, NULL, (IUnknown**) &pIRowsetFastLoad );
   CHECK_HR_ERR(hr,MainCleanup);

   // Set up parameter value binding.
   ZeroMemory( &dbBind, sizeof(dbBind) );

   struct _PARAM_BLOCK
   {
      long f1;
      long f1_status;
      long f1_length;
      unsigned long imageData_length;
      long imageData_status;
      IUnknown* imageData;
   } PARAM_BLOCK;

   dbBind[0].iOrdinal	= 1;
   dbBind[0].dwPart	= DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
   dbBind[0].eParamIO	= DBPARAMIO_NOTPARAM;
   dbBind[0].obValue	= offsetof( _PARAM_BLOCK, f1 );
   dbBind[0].obStatus	= offsetof( _PARAM_BLOCK, f1_status );
   dbBind[0].obLength	= offsetof( _PARAM_BLOCK, f1_length );
   dbBind[0].wType		= DBTYPE_I4;
   dbBind[0].cbMaxLen	= sizeof(int);

   dbBind[1].iOrdinal	= 2;
   dbBind[1].dwPart	= DBPART_VALUE | DBPART_STATUS;
   dbBind[1].eParamIO	= DBPARAMIO_NOTPARAM;
   dbBind[1].obValue	= offsetof( _PARAM_BLOCK, imageData );
   dbBind[1].obStatus	= offsetof( _PARAM_BLOCK, imageData_status );
   dbBind[1].obLength  = offsetof( _PARAM_BLOCK, imageData_length );
   dbBind[1].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
   dbBind[1].wType		= DBTYPE_IUNKNOWN;
   dbBind[1].pObject	= new DBOBJECT;
   dbBind[1].pObject->dwFlags = STGM_READ;
   dbBind[1].pObject->iid = IID_ISequentialStream;
   dbBind[1].cbMaxLen	= 0;

   // Get an accessor for our binding.
   hr = pIRowsetFastLoad->QueryInterface( IID_IAccessor, (void**)&pIAccessor );
   CHECK_HR_ERR(hr,MainCleanup);

   hr = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA,
      2,
      &dbBind[0], 
      sizeof(PARAM_BLOCK), 
      &hAccessor, 
      dbBindStatus );
   printf( "pIAccessor->CreateAccessor(...) returned 0x%08x.\n", hr );
   CHECK_HR_ERR(hr,MainCleanup);

   // Set the parameter values and status/length.
   PARAM_BLOCK.f1 = 1;
   PARAM_BLOCK.f1_status = DBSTATUS_S_OK;
   PARAM_BLOCK.f1_length = sizeof(int);
   PARAM_BLOCK.imageData = (IUnknown*)&issHelper1;
   PARAM_BLOCK.imageData_status = DBSTATUS_S_OK;
   PARAM_BLOCK.imageData_length = (long)issHelper1.m_i64ThreshHold;

   issHelper1.m_dwParamOrdinal=1;

   hr = pIRowsetFastLoad->InsertRow( hAccessor, &PARAM_BLOCK );
   CHECK_HR_ERR( hr, MainCleanup );

   hr = pIRowsetFastLoad->Commit( TRUE );
   CHECK_HR_ERR( hr, MainCleanup );

MainCleanup:

   SAFE_RELEASE( pIRowsetFastLoad );
   SAFE_RELEASE( pIOpenRowset );
   SAFE_RELEASE( pIDBCreateCommand );
   SAFE_RELEASE( pIAccessor );

   printf_s( "Test complete.\n" );
}